﻿using System;

using Microsoft.Xna.Framework;

namespace SmlEngine.Timers
{
    /// <summary>
    /// Represents a timer which regularly raises events by counting update calls.
    /// </summary>
    public sealed class FrameTimer : ITimer<int, FrameTimer>
    {
        #region Fields

        private int elapsedFrames; //The number of frames that have elapsed since the last tick event.

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the time, in frames, between two Tick events.
        /// </summary>
        public int Interval { get; set; }

        /// <summary>
        /// Gets the the total number of elapsed frames since the timer was enabled.
        /// </summary>
        public int TotalElapsed { get; private set; }

        /// <summary>
        /// Gets or sets the whether or not the timer will update and raise Tick events.
        /// </summary>
        public bool Enabled { get; set; }

        /// <summary>
        /// Gets or sets the whether or not the timer will repeat after it has raised one Tick event.
        /// </summary>
        public bool Repeats { get; set; }

        /// <summary>
        /// Gets the total number of Tick events fired since the timer was enabled.
        /// </summary>
        public int RepeatCount { get; private set; }

        /// <summary>
        /// If the timer is repeating, sets the number 
        /// of times to continue to repeat. -1 is infinite.
        /// </summary>
        public int RepeatsRemaining { get; set; }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Gets or sets the methods raised when the interval elapses
        /// </summary>
        public Action<FrameTimer> Tick { get; set; }
        
        #endregion

        #region Constructors

        internal FrameTimer() { }

        /// <summary>
        /// Constructs a new FrameTimer from the specified interval. 
        /// Note the FrameTimer begins disabled.
        /// </summary>
        /// <param name="interval">The number of frames between Tick events.</param>
        /// <param name="tick">The delegate to call when the interval has elapsed.</param>
        public FrameTimer(int interval, Action<FrameTimer> tick)
            : this(interval, tick, false) { }

        /// <summary>
        /// Constructs a new FrameTimer from the specified interval, 
        /// and whether the FrameTimer is enabled or disabled.
        /// </summary>
        /// <param name="interval">The number of frames between Tick events.</param>
        /// <param name="tick">The delegate to call when the interval has elapsed.</param>
        /// <param name="enabled">Whether or not the FrameTimer is enabled.</param>
        public FrameTimer(int interval, Action<FrameTimer> tick, bool enabled)
            : this(interval, tick, enabled, true) { }

        /// <summary>
        /// Constructs a new FrameTimer from the specified interval, whether the 
        /// FrameTimer is enabled or disabled, and whether the FrameTimer repeats.
        /// </summary>
        /// <param name="interval">The number of frames between Tick events.</param>
        /// <param name="tick">The delegate to call when the interval has elapsed.</param>
        /// <param name="enabled">Whether or not the FrameTimer is enabled.</param>
        /// <param name="repeats">Whether or not the FrameTimer repeats.</param>
        public FrameTimer(int interval, Action<FrameTimer> tick, bool enabled, bool repeats)
            : this(interval, tick, enabled, repeats, -1) { }

        /// <summary>
        /// Constructs a new FrameTimer from the specified interval, whether the 
        /// FrameTimer is enabled or disabled, and whether the FrameTimer repeats.
        /// </summary>
        /// <param name="interval">The number of frames between Tick events.</param>
        /// <param name="tick">The delegate to call when the interval has elapsed.</param>
        /// <param name="enabled">Whether or not the FrameTimer is enabled.</param>
        /// <param name="repeats">Whether or not the FrameTimer repeats.</param>
        /// <param name="repeatsRemaining">
        /// The number of times to repeat 
        /// before disabling the FrameTimer.
        /// </param>
        public FrameTimer(int interval, Action<FrameTimer> tick, bool enabled, bool repeats, int repeatsRemaining)
        {
            Interval = interval;
            Tick = tick;
            Enabled = enabled;
            Repeats = repeats;
            RepeatsRemaining = repeatsRemaining;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Updates the FrameTimer, incrementing the frame counter by one if enabled, 
        /// and raising the Tick event if the counter is equal to the interval.
        /// </summary>
        public void Update(GameTime gameTime)
        {
            Update();
        }

        /// <summary>
        /// Updates the FrameTimer, incrementing the frame counter by one if enabled, 
        /// and raising the Tick event if the counter is equal to the interval.
        /// </summary>
        public void Update()
        {
            if (Enabled)
            {
                elapsedFrames++;

                if (elapsedFrames >= Interval)
                {
                    Tick(this);

                    elapsedFrames -= Interval;

                    RepeatCount++;

                    Enabled = Repeats;

                    if (RepeatsRemaining > 0)
                        RepeatsRemaining--;
                    else if (RepeatsRemaining == 0)
                        Enabled = false;
                }
            }
        }

        /// <summary>
        /// Starts the FrameTimer by setting Enabled to true.
        /// </summary>
        public void Start()
        {
            Enabled = true;
        }

        /// <summary>
        /// Starts the FrameTimer by setting Enabled to true, 
        /// and optionally resets the frame counter.
        /// </summary>
        /// <param name="reset">Whether or not to reset the frame counter to 0.</param>
        public void Start(bool reset)
        {
            Enabled = true;

            if (reset)
                Reset();
        }

        /// <summary>
        /// Starts the FrameTimer by setting Enabled 
        /// to true, and changes the interval .
        /// </summary>
        /// <param name="interval">The new interval for the FrameTimer.</param>
        public void Start(int interval)
        {
            Enabled = true;
            Interval = interval;
        }

        /// <summary>
        /// Starts the FrameTimer by setting Enabled to true, 
        /// changing the interval, and optionally resetting the frame counter.
        /// </summary>
        /// <param name="interval">The new interval for the FrameTimer.</param>
        /// <param name="reset">Whether or not to reset the frame counter to 0.</param>
        public void Start(int interval, bool reset)
        {
            Enabled = true;
            Interval = interval;

            if (reset)
                Reset();
        }

        /// <summary>
        /// Stops the FrameTimer by setting Enabled to false.
        /// </summary>
        public void Stop()
        {
            Enabled = false;
        }

        /// <summary>
        /// Resets the frame counter to 0.
        /// </summary>
        public void Reset()
        {
            elapsedFrames = 0;
            TotalElapsed = 0;
            RepeatCount = 0;
        }

        #endregion
    }
}
